; KitchenTimer.asm
;
; A replacement software package for the Velleman MK128 Kitchen Timer.
;
; Start date: 02-Jul-2012
;
; Processsor demands: 
; PIC 16F688 has been selected as it is the first pin compatible
; chip which came to hand.
;
; Written for a 16F688 @ 4MHz. 
;
; =-=-=-=-=-= Version History =-=-=-=-=-=
;
; 2-Jul-2012
;
; Initialised project and template in conjunction with circuit diagram.
; No software work done.
;
; 15-Jul-2012
;
; It's been a rainy holiday week, so spent a few hours on this. 
; Demonstrated the principle of uniquely addressing each of the LEDs
; under timer interrupt control with 61Hz refresh rate. In order to
; display a particular pattern of LEDs, we need only instantiate a
; 16-bit word and the Timer 0 interrupt handles the rest.
; This version cycles through a programmed pattern.
;
; 16-Jul-2012
;
; Slightly more complicated pattern illustrates that multiple LEDs
; across multiple rows of the matrix can be controlled.
;
;********************************************************************** 
;
; ICSP connections are as follows: 
; ICSP   PIC   func   colour 
;   1      1   VDD     red 
;   2     13   RB7/PGD wht 
;   3     12   RB6/PGC blu 
;   4      4   /MCLR   yel 
;   5     14   VSS     bla 
;
;********************************************************************** 
;
  processor 16F688 ; PIC type 

#include <p16f688.inc> 

; internal clock, no code protect, MCLR used for I/O,
; no watchdog timer, no powerup timer, brownout detect disabled 

  __config _INTOSCIO & _CP_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _BOD_OFF & _IESO_OFF & _FCMEN_OFF

  __idlocs 0xC11C 

; #define Debug -1 

#IFDEF Debug 
  MESSG "*** DEBUG BUILD !! ***" 
  MESSG "*** DEBUG BUILD !! ***" 
  MESSG "*** DEBUG BUILD !! ***" 
#ENDIF 


;********************************************************************** 
; Useful Constants 
;********************************************************************** 


;********************************************************************** 
; I/O pins and registers 
;********************************************************************** 

Vin         equ RA5 ; pin  2 ; originally RB5
Gnd         equ RC4 ; pin  6 ;

Buz         equ RC5 ; pin  5 ;

Row1        equ RA0 ; pin 13 ; originally RB0
Row2        equ RA1 ; pin 12 ; originally RB1
Row3        equ RA2 ; pin 11 ; originally RB2
Row4        equ RA4 ; pin  3 ; originally RB3

Col1        equ RC0 ; pin 10 ;
Col2        equ RC1 ; pin  9 ;
Col3        equ RC2 ; pin  8 ;
Col4        equ RC3 ; pin  7 ;


;********************************************************************** 
; Flags bits 
;********************************************************************** 



;********************************************************************** 
; static RAM GPRs 
;********************************************************************** 

; Define variables in static RAM (General Purpose Registers) 

GPRs udata                   ; start of udata typically 0x20

Frame            res  2      ; state of all 16 LEDs (H:L)

PattIndex        res  1      ; index into pattern sequence

PORTAtemp        res  1      ; temp copy of PORTA for R-M-W operations
PORTCtemp        res  1      ; temp copy of PORTC for R-M-W operations

LEDcol           res  1      ; column of LEDs being refreshed

Shared udata_shr

Flags            res  1      ; flags bits
W_temp           res  1      ; temp copy of W for context saving
STATUS_temp      res  1      ; temp copy of STATUS for context saving
FSR_temp         res  1      ; temp copy of FSR for context saving
PCLATH_temp      res  1      ; temp copy of PCLATH for context saving


;********************************************************************** 
; Macro definitions 
;********************************************************************** 

M_CMPJGEW MACRO Reg, Addr ; compare and jump if Reg >= W 

  SUBWF Reg, W               ; W = Reg - W
  BTFSC STATUS, C            ; If !carry is clear, there was carry
  GOTO Addr                  ; so W > Reg so skip the GOTO 

  ENDM ; M_CMPJGEW


; **********************************************************************

M_CMPILTR MACRO Reg1, Reg2 ; compare and increment Reg1 if Reg1 < Reg2

  MOVF Reg2, W
  SUBWF Reg1, W              ; W = Reg1 - Reg2

  BTFSS STATUS, C            ; If !carry is set, there was no carry,
  INCF Reg1, F               ; so Reg2 <= Reg1 so skip the increment

  ENDM ; M_CMPILTR


; **********************************************************************

M_CMPDGTR MACRO Reg1, Reg2 ; compare and decrement Reg1 if Reg1 > Reg2

  MOVF Reg1, W
  SUBWF Reg2, W              ; W = Reg2 - Reg1

  BTFSS STATUS, C            ; If !carry is set, there was no carry,
  DECF Reg1, F               ; so Reg1 <= Reg2 so skip the decrement

  ENDM ; M_CMPDGTR


; **********************************************************************

M_PWM MACRO Cnt, Dem, OutByte, OutBit

  MOVF Dem, W
  SUBWF Cnt, W               ; W = Cnt - Dem
  BSF OutByte, OutBit        ; set output bit
  BTFSC STATUS, C            ; If !carry is clear, there was carry
  BCF OutByte, OutBit        ; i.e. Dem > Cnt so skip the BCF 

  ENDM ; M_PWM


; **********************************************************************

M_AssertRows MACRO X, Y

  BTFSC Frame +1, X          ; if bit X of LS byte
  BCF PORTAtemp, Row1        ; assert (low) row 1
  BTFSC Frame +1, Y          ; if bit Y of LS byte
  BCF PORTAtemp, Row2        ; assert (low) row 2
  BTFSC Frame, X             ; if bit X of MS byte
  BCF PORTAtemp, Row3        ; assert (low) row 3
  BTFSC Frame, Y             ; if bit Y of MS byte
  BCF PORTAtemp, Row4        ; assert (low) row 4

  ENDM ; M_AssertRows


; ********************************************************************** 

EEPROM CODE 0x2100           ; start of EEPROM
  DE d'31', d'136'           ; servo count max and min


; ********************************************************************** 

Begin CODE 0x0000           ; start address
  GOTO Init


; ********************************************************************** 
; Interrupt vector 
; ********************************************************************** 

Intr CODE 0x0004             ; interrupt vector 
  GOTO IntVector             ; moved to allow more room for tables 


; ********************************************************************** 
; Other useful lookup tables 
; ********************************************************************** 

LUTpatt:

  ADDWF PCL, F
  DT .66 ; size of table
  DT b'00000000', b'00000011'
  DT b'00000000', b'00000111'
  DT b'00000000', b'00001111'
  DT b'00000000', b'00011110'
  DT b'00000000', b'00111100'
  DT b'00000000', b'01111000'
  DT b'00000000', b'11110000'
  DT b'00000001', b'11100000'
  DT b'00000011', b'11000000'
  DT b'00000111', b'00000000'
  DT b'00001111', b'00000000'
  DT b'00011110', b'00000000'
  DT b'00111100', b'00000000'
  DT b'01111000', b'00000000'
  DT b'11110000', b'00000000'
  DT b'11100000', b'00000000'
  DT b'11000000', b'00000000'
  DT b'11100000', b'00000000'
  DT b'11110000', b'00000000'
  DT b'01111000', b'00000000'
  DT b'00111100', b'00000000'
  DT b'00111100', b'00000000'
  DT b'00011110', b'00000000'
  DT b'00001111', b'00000000'
  DT b'00000111', b'10000000'
  DT b'00000011', b'11000000'
  DT b'00000001', b'11100000'
  DT b'00000000', b'11110000'
  DT b'00000000', b'01111000'
  DT b'00000000', b'00111100'
  DT b'00000000', b'00011110'
  DT b'00000000', b'00001111'
  DT b'00000000', b'00000111'


;********************************************************************** 
; New Interrupt Vector 
;********************************************************************** 

IntVector: 

; Interrupts are serviced here instead of at 0x0004 as this permits the 
; creation of the lookup tables before the end of the first memory page. 

; save context 

  errorlevel -302 

  MOVWF W_temp               ; Copy W to TEMP register, 
  SWAPF STATUS, W            ; Swap status to be saved into W 
  MOVWF STATUS_temp          ; Save status to STATUStemp register 
  MOVF FSR, W                ; temp copy of FSR 
  MOVWF FSR_temp 

  errorlevel +302 

TestTimer0Int:
  banksel INTCON
  BTFSC INTCON, T0IF         ; was it a timer 0 interrupt?
  CALL Tmr0Handler           ; otherwise

TestTimer1Int:
  banksel PIR1
  BTFSC PIR1, TMR1IF         ; was it a timer 1 interrupt?
  CALL Tmr1Handler
    

;--- other interrupt tests go here ---

DoneInt: 

  ; restore context 

  errorlevel -302 

  MOVF FSR_temp, W           ; restore FSR 
  MOVWF FSR 
  SWAPF STATUS_temp, W       ; swap nibbles in STATUStemp, result in W 
  MOVWF STATUS               ; W into STATUS register (sets bank to original state) 
  SWAPF W_temp, F            ; swap nibbles in Wtemp 
  SWAPF W_temp, W            ; swap nibbles in Wtemp, result into W 

  errorlevel +302 

  RETFIE                     ; no - end ISR 


;********************************************************************** 

Tmr0Handler:

; To minimise flicker, the display should be refreshed at
; greater than 24Hz. Ideally, 60Hz. At a 4MHz clock rate, this
; allows 16666 operations to process. More than enough.

; But this won't work: each LED needs to be addressed individually.
; We could service one LED per interrupt, but that would require
; 16 times the number of interrupts: 1 every 1667 instructions.
; Best to address an entire column of LEDs per interrupt, so we
; only interrupt every 4096 operations.

; Refresh is by way of a 8 bit timer 0 interrupt with a prescaler
; of 16. This causes a refresh every 4096 instructions. At 4MHz,
; with 4 rows to refresh, this is a total rate of 1 / 256 * 16 * 4
; = 61.03Hz.

; set the column to be refreshed

  banksel LEDcol
  RRF LEDcol, F              ; LEDcol contains a walking bit
  BTFSC STATUS, C
  BSF LEDcol, 3              ; which moves accross four places

  banksel PORTC
  MOVF PORTC, W              ; temp copy of PORTC
  ANDLW 0xF0                 ; blank low nybble
  banksel LEDcol
  IORWF LEDcol, W            ; OR in the anode (column)
  banksel PORTCtemp
  MOVWF PORTCtemp

; set the rows as required

  banksel PORTA
  MOVF PORTA, W              ; temp copy of PORTA
  banksel PORTAtemp
  MOVWF PORTAtemp

  BSF PORTAtemp, Row1        ; start by clearing (high) the row bits
  BSF PORTAtemp, Row2
  BSF PORTAtemp, Row3
  BSF PORTAtemp, Row4

; Frame    Frame +1
; (MSB)    (LSB)
; 00000000 00000000

TestCol1:
  BTFSS LEDcol, 0            ; if we're refreshing col 1
  GOTO TestCol2
  M_AssertRows 0, 4

TestCol2:
  BTFSS LEDcol, 1            ; if we're refreshing col 2
  GOTO TestCol3
  M_AssertRows 1, 5

TestCol3:
  BTFSS LEDcol, 2            ; if we're refreshing col 3
  GOTO TestCol4
  M_AssertRows 2, 6

TestCol4:
  BTFSS LEDcol, 3            ; if we're refreshing col 4
  GOTO TestColDone
  M_AssertRows 3, 7

TestColDone:

; copy modified DIO bytes back onto ports

  MOVF PORTAtemp, W  
  MOVWF PORTA

  MOVF PORTCtemp, W  
  MOVWF PORTC

; done

  BCF INTCON, T0IF           ; allow further timer 0 interrupts

  RETURN ; Tmr0Handler


;********************************************************************** 

Tmr1Handler: ; slow loop

; test code by cycling through a pattern

  BCF INTCON, T0IE           ; disable while frame being modified

  banksel PattIndex

  INCF PattIndex, F          ; lookup pattern high byte
  MOVF PattIndex, W
  CALL LUTpatt
  MOVWF Frame                ; insert into frame buffer

  INCF PattIndex, F          ; lookup pattern low byte
  MOVF PattIndex, W
  CALL LUTpatt
  MOVWF Frame +1             ; insert into frame buffer

  BSF INTCON, T0IE           ; re-enable frame buffer display

  MOVLW 0x00                 ; lookup pattern table size
  CALL LUTpatt

  banksel PattIndex
  XORWF PattIndex, W         ; reset pattern index as required
  BTFSC STATUS, Z
  CLRF PattIndex

  banksel PIR1
  BCF PIR1, TMR1IF           ; allow further timer 1 interrupts

  RETURN ; Tmr1Handler


;********************************************************************** 

Init: 

; clear ports 
  banksel PORTA              ; (set bank 0) 
  CLRF PORTA                 ; clear ports 
  CLRF PORTC

  banksel CMCON0
  MOVLW 0x07                 ; comparator inputs configured as DIO
  IORWF CMCON0, F

  errorlevel -302 

  banksel ANSEL              ; bank 1
  CLRF ANSEL                 ; set AI ports as DIO

  MOVLW 0x03
  MOVWF PCON                 ; no brownout or power on reset ocurred

; internal oscillator speed

;  MOVLW b'01110000'           ; INTOSC at 8MHz
  MOVLW b'01100000'           ; INTOSC at 4MHz
  MOVWF OSCCON                ; 41203D, para 3.2 & 3.6.1
  
; set up I/O 

  MOVLW 0xFF
  banksel PORTAtemp
  MOVWF PORTAtemp
  BCF PORTAtemp, Row1
  BCF PORTAtemp, Row2
  BCF PORTAtemp, Row3
  BCF PORTAtemp, Row4
  MOVF PORTAtemp, W
  banksel TRISA
  MOVWF TRISA                ; all ports as input except the above

  MOVLW 0xFF
  banksel PORTCtemp
  MOVWF PORTCtemp
  BCF PORTCtemp, Buz
  BCF PORTCtemp, Col1
  BCF PORTCtemp, Col2
  BCF PORTCtemp, Col3
  BCF PORTCtemp, Col4
  MOVF PORTCtemp, W
  banksel TRISC
  MOVWF TRISC                ; all ports as input except the above

; set up timer 0 

  MOVLW b'00000011' ; pull-ups enabled, 1:16 prescaler
  MOVWF OPTION_REG

  errorlevel +302 

; set up timer 1 

  banksel T1CON
  BSF T1CON, TMR1ON
  BSF T1CON, T1CKPS1 ; 1:4 presacler 256^2*4 instructions
  BCF T1CON, T1CKPS1 ; 1:2 presacler DEBUG
  BCF T1CON, T1CKPS0 ; = appx 0.25sec

; set up interrupts 

  errorlevel -302 

  banksel PIE1
  BSF PIE1, TMR1IE

  errorlevel +302 

  BCF INTCON, T0IF           ; clear timer 0 interrupt flag 
  BSF INTCON, T0IE           ; unmask TMR0 interrupt 

  BSF INTCON, GIE            ; global unmasked interrupt enable 


; ********************************************************************** 

Main: 

  banksel LEDcol
  MOVLW 0x01
  MOVWF LEDcol               ; initialise walking bit

  banksel PattIndex          ; reset pattern index
  CLRF PattIndex

Loop:

  GOTO Loop

  END 